﻿/*
Copyright (c) 2013, SonicLing
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

    Redistributions of source code must retain the above copyright notice, this 
list of conditions and the following disclaimer.
    Redistributions in binary form must reproduce the above copyright notice, 
this list of conditions and the following disclaimer in the documentation and/or 
other materials provided with the distribution.
    Neither the name of the SonicLing nor the names of its contributors may be 
used to endorse or promote products derived from this software without specific 
prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using XmlConfiguration;
using System.Xml.Serialization;
using System.Collections.ObjectModel;

namespace Bibtex2Word
{
	public partial class Form1 : Form
	{
		public Form1()
		{
			InitializeComponent();
			Text = Properties.Settings.Default.MainTitle;
			ShowBibFileList();
		}

		FormSettings UISettings = new FormSettings();
		WordManipulator wordManipulator = new WordManipulator();

		private string ConfigFile
		{
			get
			{
				string path = System.Reflection.Assembly.GetEntryAssembly().CodeBase;
				if (path.StartsWith("file:///"))
				{
					path = path.Substring(8);
				}
				return System.IO.Path.Combine(System.IO.Path.GetDirectoryName(path), "Bibtex2Word.config.xml");
			}
		}

		private void Form1_Load(object sender, EventArgs e)
		{
			if (System.IO.File.Exists(ConfigFile))
			{
				XmlConfigFile.Load(ConfigFile, UISettings);
			}
			bibList.ShowGroups = showGroupsToolStripMenuItem.Checked = UISettings.ShowGroups;
			for (int i = 0; i < UISettings.ColumnWidths.Count && i < bibList.Columns.Count; i++)
			{
				bibList.Columns[i].Width = UISettings.ColumnWidths[i];
			}
			Width = UISettings.Width;
			Height = UISettings.Height;
			Top = UISettings.Top;
			Left = UISettings.Left;

			if (System.IO.File.Exists(UISettings.LastSourceFile))
			{
				Source = new PathItem(UISettings.LastSourceFile);
				LoadSource();
				ShowSourceList();
			}
			ShowBibFileList();
		}

		private void Form1_FormClosing(object sender, FormClosingEventArgs e)
		{
			if (Dirty)
			{
				DialogResult dr = MessageBox.Show("Bibliography list is changed, do you want to save it?", "Saving Sources", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
				if (dr == DialogResult.Cancel)
				{
					e.Cancel = true;
					return;
				}
				else if (dr == DialogResult.Yes)
				{
					saveToolStripMenuItem_Click(sender, e);
					if (Dirty)
					{
						e.Cancel = true;
						return;
					}
				}
			}
			UISettings.ShowGroups = showGroupsToolStripMenuItem.Checked;
			UISettings.ColumnWidths.Clear();
			for (int i = 0; i < bibList.Columns.Count; i++)
			{
				UISettings.ColumnWidths.Add(bibList.Columns[i].Width);
			}
			UISettings.Width = Width;
			UISettings.Height = Height;
			UISettings.Top = Top;
			UISettings.Left = Left;
			XmlConfigFile.Save(ConfigFile, UISettings);
			// close manipulator
			wordManipulator.Close();
		}

		PathItem Source;
		bool Dirty = false;
		WordBibSources WordBib = new WordBibSources();
		TranslationProject Project = new TranslationProject();

		string ProjectFile
		{
			get { return Source.Path + ".b2w"; }
		}

		private void LoadSource()
		{
			if (Source != null && Source.IsExisted)
			{
				WordBib = XmlConfigFile.Load<WordBibSources>(Source.Path, Encoding.UTF8);
				ShowMessage("Loaded {0} article(s).", WordBib.Sources.Count);
				if (System.IO.File.Exists(ProjectFile))
				{
					Project = XmlConfigFile.Load<TranslationProject>(ProjectFile);
				}
				Text = string.Format(Properties.Settings.Default.MainTitlePattern, Properties.Settings.Default.MainTitle, Source.Path);
				Dirty = false;
			}
		}

		private void SaveSource()
		{
			XmlConfigFile.SaveSerializable(Source.Path, WordBib, Encoding.UTF8);
			ShowMessage("{0} has been saved.", Source.Path);
			XmlConfigFile.Save(ProjectFile, Project);
			Dirty = false;
		}

		private void newSourceToolStripMenuItem_Click(object sender, EventArgs e)
		{
			Source = null;
			WordBib.Sources.Clear();
			Project.Reset();
			Dirty = false;

			ShowSourceList();
			ShowBibFileList();
		}

		private void openToolStripMenuItem_Click(object sender, EventArgs e)
		{
			if (openFileDlg.ShowDialog(this) == DialogResult.OK)
			{
				Source = new PathItem(openFileDlg.FileName);
				// load source
				LoadSource();
				UISettings.LastSourceFile = Source.Path;
				// display source
				ShowSourceList();
				ShowBibFileList();
			}
		}

		private void saveToolStripMenuItem_Click(object sender, EventArgs e)
		{
			if (WordBib != null)
			{
				if (Source != null && Source.IsExisted)
				{
					SaveSource();
				}
				else
				{
					saveAsToolStripMenuItem_Click(sender, e);
				}
			}
		}

		private void saveAsToolStripMenuItem_Click(object sender, EventArgs e)
		{
			if (WordBib != null && saveFileDlg.ShowDialog(this) == DialogResult.OK)
			{
				Source = new PathItem(saveFileDlg.FileName);
				SaveSource();
				UISettings.LastSourceFile = Source.Path;
				this.Text = string.Format(Properties.Settings.Default.MainTitlePattern, Properties.Settings.Default.MainTitle, Source.Path);
			}
		}

		BibTranslator bibTranslator = new BibTranslator();
		private void AddBibFiles(IEnumerable<string> files)
		{
			List<WordBibSource> Added = new List<WordBibSource>();
			int total = 0;
			int fileCount = 0;
			foreach (string file in files)
			{
				PathItem bib = new PathItem(file);
				if (Project.AddBibFile(bib))
				{
					fileCount++;
					WordBibSource[] sources = bibTranslator.Translate(bib);
					total += sources.Count();
					IEnumerable<WordBibSource> unique = sources.SkipWhile((s) => WordBib.Sources.Any((t) => s.Title.Equals(t.Title)) || Added.Any((t) => s.Title.Equals(t.Title)));
					if (sources.Length > 0)
					{
						fileList.Items.Add(bib);
						foreach (WordBibSource source in unique)
						{
							Project.BibMap[bib].Add(source.Guid.ToString("b"));
						}
						Added.AddRange(unique);
					}
					else
					{
						Project.BibMap.Remove(bib);
					}
				}
			}
			WordBib.Sources.AddRange(Added);
			Dirty = true;
			ShowMessage("Analyzed {0} file(s), {1} added, {2} duplicated.", fileCount, Added.Count, total - Added.Count);
			ShowBibFileList();
			ShowSourceList();
			SelectSources(Added);
		}

		private void addBibtextToolStripMenuItem_Click(object sender, EventArgs e)
		{
			if (openBibDlg.ShowDialog(this) == DialogResult.OK)
			{
				AddBibFiles(openBibDlg.FileNames);
			}
		}

		private void addBibtexDirectoryToolStripMenuItem_Click(object sender, EventArgs e)
		{
			folderDlg.SelectedPath = UISettings.LastDirectory;
			if (folderDlg.ShowDialog(this) == DialogResult.OK)
			{
				UISettings.LastDirectory = folderDlg.SelectedPath;
				AddBibFiles(System.IO.Directory.EnumerateFiles(folderDlg.SelectedPath, "*.bib"));
			}
		}

		private string[] showList = null;
		private void ShowSourceList()
		{
			bibList.Items.Clear();
			if (WordBib != null)
			{
				Resort();
				foreach (WordBibSource source in WordBib.Sources)
				{
					if (showList == null || showList.Contains(source.Guid.ToString("b")))
					{
						bibList.Items.Add(new BibSourceItem(source, bibList));
					}
				}
			}
		}

		private void ShowBibFileList()
		{
			fileList.Items.Clear();
			fileList.Items.Add("[All]");
			if (Project != null)
			{
				foreach (PathItem path in Project.BibMap.Keys)
				{
					fileList.Items.Add(path);
				}
			}
		}

		private void ShowMessage(string message, params object[] args)
		{
			string msg = string.Format(message, args);
			msgList.SetSelected(msgList.Items.Add(string.Format("{0}\n", msg)), true);
			lastMessage.Text = msg;
		}

		private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
		{
			Form2 about = new Form2();
			about.ShowDialog(this);
		}

		private void exitToolStripMenuItem_Click(object sender, EventArgs e)
		{
			Close();
		}

		private void sortToolStripMenuItem_DropDownOpening(object sender, EventArgs e)
		{
			byTagToolStripMenuItem.Checked = UISettings.SortBy == SortType.Tag;
			byTitleToolStripMenuItem.Checked = UISettings.SortBy == SortType.Title;
			byYearToolStripMenuItem.Checked = UISettings.SortBy == SortType.Year;
		}

		private void sortByClick(object sender, EventArgs e)
		{
			byTagToolStripMenuItem.Checked = false;
			byTitleToolStripMenuItem.Checked = false;
			byYearToolStripMenuItem.Checked = false;

			(sender as ToolStripMenuItem).Checked = true;
			UISettings.SortBy = (SortType)Enum.Parse(typeof(SortType), (sender as ToolStripMenuItem).Tag.ToString());
			Resort();
			ShowSourceList();
		}

		private void Resort()
		{
			switch (UISettings.SortBy)
			{
				case SortType.Tag:
					WordBib.Sources.Sort((s1, s2) => s1.Tag.CompareTo(s2.Tag));
					break;
				case SortType.Title:
					WordBib.Sources.Sort((s1, s2) => s1.Title.CompareTo(s2.Title));
					break;
				case SortType.Year:
					WordBib.Sources.Sort((s1, s2) => {
						try
						{
							int y1 = int.Parse(s1.Year), y2 = int.Parse(s2.Year);
							if (y1 == y2)
							{
								return s1.Title.CompareTo(s2.Title);
							}
							return y2 - y1;
						}
						catch (System.Exception )
						{
							return s1.Title.CompareTo(s2.Title);
						}
					});
					break;
			}
		}

		private void copyTagToolStripMenuItem_Click(object sender, EventArgs e)
		{
			try
			{
				CopySelectedTags((s, bs) => wordManipulator.GetCitationField(s, bs).Copy(), "copied to clipboard");
			}
			catch (System.Exception ex)
			{
				System.Windows.Forms.MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
			}
		}

		private void CopySelectedTags(Action<string, WordBibSource[]> copyAction, string comment)
		{
			if (bibList.SelectedItems.Count > 0)
			{
				StringBuilder tags = new StringBuilder();
				StringBuilder titles = new StringBuilder();
				int count = 0;
				List<WordBibSource> selected = new List<WordBibSource>();
				foreach (ListViewItem item in bibList.SelectedItems)
				{
					WordBibSource s = (item as BibSourceItem).Source;
					tags.Append(string.Format(count == 0 ? "{0} \\l {1}" : " \\m {0}", s.Tag, s.LCID));
					selected.Add(s);
					if (count++ < 1)
					{
						titles.Append(string.Format("\"{0}\", ", (item as BibSourceItem).Source.Title));
					}
					else if (!titles.ToString().EndsWith(" etc."))
					{
						titles.Append(" etc.");
					}
				}
				while (tags.Length > 0 && tags.ToString().EndsWith(","))
				{
					tags.Remove(tags.Length - 1, 1);
				}
				copyAction(tags.ToString(), selected.ToArray());
				if (titles.ToString().EndsWith(", "))
				{
					titles.Remove(titles.Length - 2, 2);
				}
				string pl = count > 1 ? "s" : "";
				string be = count > 1 ? "are" : "is";
				ShowMessage("Tag{0} of {1} {2} {3}.", pl, titles, be, comment);
			}
		}

		private void pasteBibtexToolStripMenuItem_Click(object sender, EventArgs e)
		{
			if (Clipboard.GetData(DataFormats.Text) != null)
			{
				string text = Clipboard.GetData(DataFormats.Text) as string;
				AddBibText(text);
			}
		}

		private void AddBibText(string text)
		{
			WordBibSource[] newlyAdded = bibTranslator.Translate(text);
			IEnumerable<WordBibSource> unique = newlyAdded.SkipWhile((s) => WordBib.Sources.Any((t) => s.Title.Equals(t.Title)));
			foreach (WordBibSource source in unique)
			{
				Project.BibMap[PathItem.Clipboard].Add(source.Guid.ToString("b"));
			}
			ShowMessage("Extracted {0} Articles, {1} duplicated.", unique.Count(), newlyAdded.Count() - unique.Count());
			WordBib.Sources.AddRange(unique);
			Dirty = true;
			fileList_SelectedIndexChanged(fileList, new EventArgs());
			if (fileList.Items.Count == 0)
			{
				ShowBibFileList();
			}
			ShowSourceList();
			SelectSources(newlyAdded);
		}

		private void SelectSources(IEnumerable<WordBibSource> sources)
		{
			foreach (ListViewItem item in bibList.Items)
			{
				foreach (WordBibSource source in sources)
				{
					if ((item as BibSourceItem).Source == source)
					{
						item.Selected = true;
						item.EnsureVisible();
					}
				}
			}
			if (bibList.SelectedItems.Count > 0)
			{
				bibList.Select();
			}
		}

		private void bibList_ColumnClick(object sender, ColumnClickEventArgs e)
		{
			if (e.Column == 0)
				sortByClick(byTagToolStripMenuItem, e);
			else if (e.Column == 2)
				sortByClick(byTitleToolStripMenuItem, e);
			else if (e.Column == 3)
				sortByClick(byYearToolStripMenuItem, e);
		}

		private void fileList_SelectedIndexChanged(object sender, EventArgs e)
		{
			if (fileList.SelectedItems.Count > 0)
			{
				object selected = fileList.SelectedItems[0];
				if (typeof(PathItem).IsInstanceOfType(selected))
				{
					showList = Project.BibMap[selected as PathItem].ToArray();
				}
				else if (typeof(string).IsInstanceOfType(selected))
				{
					showList = null;
				}
			}
			ShowSourceList();
		}

		private void Form1_DragEnter(object sender, DragEventArgs e)
		{
			if (e.Data.GetDataPresent(DataFormats.Text) || e.Data.GetDataPresent(DataFormats.FileDrop))
			{
				e.Effect = DragDropEffects.Copy;
			}
		}

		private void Form1_DragDrop(object sender, DragEventArgs e)
		{
			if (e.Data.GetDataPresent(DataFormats.Text))
			{
				AddBibText(e.Data.GetData(DataFormats.Text) as string);
			}
			else if (e.Data.GetDataPresent(DataFormats.FileDrop))
			{
				AddBibFiles(e.Data.GetData(DataFormats.FileDrop) as string[]);
			}
		}

		private void selectAllToolStripMenuItem_Click(object sender, EventArgs e)
		{
			foreach (ListViewItem item in bibList.Items)
			{
				item.Selected = true;
			}
		}

		private bool ShowGroups
		{
			get { return showGroupsToolStripMenuItem.Checked; }
		}

		private void showGroupsToolStripMenuItem_Click(object sender, EventArgs e)
		{
			showGroupsToolStripMenuItem.Checked = !showGroupsToolStripMenuItem.Checked;
			bibList.ShowGroups = ShowGroups;
			ShowSourceList();
		}

		private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
		{
			selectAllToolStripMenuItem.Enabled = bibList.Items.Count > 0;
			deleteToolStripMenuItem.Enabled = bibList.SelectedItems.Count > 0;
		}

		private void deleteToolStripMenuItem_Click(object sender, EventArgs e)
		{
			List<ListViewItem> items = new List<ListViewItem>();
			List<WordBibSource> sources = new List<WordBibSource>();
			foreach (ListViewItem item in bibList.SelectedItems)
			{
				if (typeof(BibSourceItem).IsInstanceOfType(item))
				{
					WordBibSource source = (item as BibSourceItem).Source;
					sources.Add(source);
				}
				items.Add(item);
			}
			if (MessageBox.Show(string.Format("Delete {0} selected sources?", sources.Count), "Confirmation", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
			{
				sources.ForEach(source => WordBib.Sources.Remove(source));
				items.ForEach(item => bibList.Items.Remove(item));
				Dirty = (sources.Count > 0) || Dirty;
			}
		}

		private void copyTitleToolStripMenuItem_Click(object sender, EventArgs e)
		{
			List<string> selected = new List<string>();
			foreach (ListViewItem item in bibList.SelectedItems)
			{
				if (typeof(BibSourceItem).IsInstanceOfType(item))
				{
					selected.Add((item as BibSourceItem).Source.Title);
				}
			}
			if (selected.Count > 0)
			{
				string titles = selected.Aggregate((s1, s2) => s1 + ", " + s2);
				Clipboard.SetData(DataFormats.UnicodeText, titles);
				ShowMessage("{0} copied.", titles);
			}
		}
	}
}
